﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using Common.Logging;
using Quartz;
using Quartz.Collection;
using Quartz.Core;
using Quartz.Simpl;
using Quartz.Spi;
using SimpleScheduler.Common;
using SimpleScheduler.Core.Common;

namespace SimpleScheduler.Server.Services
{
    public class XmlJobStore : IJobStore
    {
        private string folderPath;

        private ReaderWriterLockSlim readerWriterLock = new ReaderWriterLockSlim();
        private readonly ILog log;
        private ISchedulerSignaler signaler;

        public XmlJobStore()
	    {
	        log = LogManager.GetLogger(GetType());
	    }	   

        protected internal static string ResolveFile(string fName)
        {
            if (fName != null && fName.StartsWith("~"))
            {
                // relative to run directory
                fName = fName.Substring(1);
                if (fName.StartsWith("/") || fName.StartsWith("\\"))
                {
                    fName = fName.Substring(1);
                }
                fName = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, fName);
            }

            return fName;
        }

        
		public virtual void Initialize(ITypeLoadHelper loadHelper, ISchedulerSignaler s)
		{
			signaler = s;            
            
	        folderPath = ResolveFile("~/JobStore/");
            if (!Directory.Exists(folderPath)) Directory.CreateDirectory(folderPath);

            // Load all jobs currently in directory
            //LoadJobs();

			log.Info("XmlJobStore initialized.");
		}      

        //private void LoadJobs()
        //{
        //    var directoryInfo = new DirectoryInfo(folderPath);
        //    foreach (var directory in directoryInfo.GetDirectories())
        //    {
        //        foreach (var file in directory.GetFiles("*.xml"))
        //        {
        //            LoadJob(directory, file);
        //        }
        //    }
        //}

        private IEnumerable<DirectoryInfo> GetGroups()
        {
            var directoryInfo = new DirectoryInfo(folderPath);
            foreach (var directory in directoryInfo.GetDirectories())
            {
                yield return directory;
            }
        }

        private DirectoryInfo GetGroup(string groupName)
        {
            var group = GetGroups().Where(d => d.Name == groupName).FirstOrDefault();
            if (group == null)
            {
                var directoryInfo = new DirectoryInfo(Path.Combine(folderPath, groupName));
                directoryInfo.Create();
                return GetGroups().Where(d => d.Name == groupName).FirstOrDefault();
            }
            return group;
        }

        private IEnumerable<FileInfo> GetJobs()
        {
            foreach (var group in GetGroups())
            {
                foreach (var file in group.GetFiles("*.xml"))
                {
                    yield return file;
                }
            }
        }

        private IEnumerable<FileInfo> GetJobs(DirectoryInfo Group)
        {
            foreach(var file in Group.GetFiles("*.xml"))
            {
                yield return file;
            }
        }

        private FileInfo GetJob(DirectoryInfo Group, string jobName)
        {
            return GetJobs(Group).Where(j => j.Name == jobName).FirstOrDefault();
        }

        private IEnumerable<Core.Domain.Trigger> GetTriggers()
        {
            return GetGroups().SelectMany(g => GetJobs(g).SelectMany(j => LoadJob(j).Triggers));
        }

        private IEnumerable<Core.Domain.Trigger> GetTriggers(string groupName)
        {
            return GetGroups().Where(g => g.Name == groupName).SelectMany(g => GetJobs(g).SelectMany(j => LoadJob(j).Triggers));
        }

        private IEnumerable<Trigger> GetQuartzTriggers()
        {
            var groups = GetGroups();
            foreach(var group in groups)
            {
                var jobs = GetJobs(group);
                foreach(var job in jobs)
                {
                    foreach(var trigger in LoadJob(job).Triggers)
                    {
                        yield return ConvertTrigger(job.Name, group.Name, trigger);
                    }
                }
            }
        }

        private Core.Domain.Job LoadJob(FileInfo file)
        {
            if (!file.Extension.Equals(".xml", StringComparison.InvariantCultureIgnoreCase))
            {
                return null;
            }

            string xml;

            using (var fs = file.OpenText())
            {
                xml = fs.ReadToEnd();
                fs.Close();
            }

            return xml.XmlDeserialize<Core.Domain.Job>();
        }

        private Trigger ConvertTrigger(string jobName, string groupName, Core.Domain.Trigger trigger)
        {
            Trigger newTrigger = null;
            switch (trigger.Type)
            {
                case "Simple":
                    SimpleTrigger simpleTrigger = new SimpleTrigger(trigger.Name, groupName);

                    if (trigger.StartTime.HasValue)
                        simpleTrigger.StartTimeUtc = trigger.StartTime.Value.ToUniversalTime();
                    if (trigger.EndTime.HasValue)
                        simpleTrigger.EndTimeUtc = trigger.EndTime.Value.ToUniversalTime();

                    simpleTrigger.RepeatCount = (trigger.RepeatCount == null) ? SimpleTrigger.RepeatIndefinitely : trigger.RepeatCount.Value;
                    if (trigger.RepeatInterval.HasValue)
                        simpleTrigger.RepeatInterval = TimeSpan.FromSeconds(trigger.RepeatInterval.Value);
                    simpleTrigger.JobName = jobName;
                    simpleTrigger.JobGroup = groupName;
                    simpleTrigger.MisfireInstruction = MisfireInstruction.SimpleTrigger.RescheduleNextWithExistingCount;

                    newTrigger = simpleTrigger;
                    break;

                case "Cron":
                    CronTrigger cronTrigger = new CronTrigger(trigger.Name, groupName);

                    var year = trigger.Year ?? "*";
                    var month = trigger.Month ?? "*";
                    var day = trigger.Day ?? "*";
                    var hour = trigger.Hour ?? "0";
                    var minute = trigger.Minute ?? "0";
                    var second = trigger.Second ?? "0";
                    var dayOfWeek = (day != "*") ? "?" : trigger.DayOfWeek ?? "?";

                    cronTrigger.CronExpressionString = string.Format("{0} {1} {2} {3} {4} {5} {6}", second, minute, hour, day, month, dayOfWeek, year);
                    cronTrigger.JobName = jobName;
                    cronTrigger.JobGroup = groupName;
                    cronTrigger.MisfireInstruction = MisfireInstruction.CronTrigger.DoNothing;

                    newTrigger = cronTrigger;
                    break;
            }

            newTrigger.ComputeFirstFireTimeUtc(null);
            return newTrigger;
        }

        public virtual void SchedulerStarted()
		{
			// nothing to do
		}

		public virtual void Shutdown()
		{            
		}

        public void StoreJobAndTrigger(JobDetail newJob, Trigger newTrigger)
        {
            
        }

        public bool IsJobGroupPaused(string groupName)
        {
            return false;
        }

        public bool IsTriggerGroupPaused(string groupName)
        {
            return false;
        }

        public void StoreJob(JobDetail newJob, bool replaceExisting)
        {
            var file = new FileInfo(Path.Combine(Path.Combine(folderPath, newJob.Group), newJob.Name) + ".xml");
            using(var fs = new StreamWriter(file.FullName))
            {
                fs.Write(newJob.XmlSerialize());
                fs.Close();
            }
        }

        public bool RemoveJob(string jobName, string groupName)
        {
            return false;
        }

        public JobDetail RetrieveJob(string jobName, string groupName)
        {
            var groupDir = GetGroup(groupName);
            var jobFile = GetJob(groupDir, jobName);
            var job = LoadJob(jobFile);

            JobDetail quartzJob = new JobDetail(jobName, groupName, Type.GetType(job.Type));
            quartzJob.Description = job.Description;
            quartzJob.JobDataMap["Tasks"] = job.Tasks;

            return quartzJob;
        }

        
        public void StoreTrigger(Trigger newTrigger, bool replaceExisting)
        {
            
        }

        public bool RemoveTrigger(string triggerName, string groupName)
        {
            return false;
        }

        public bool ReplaceTrigger(string triggerName, string groupName, Trigger newTrigger)
        {
            return false;
        }

        public Trigger RetrieveTrigger(string triggerName, string groupName)
        {
            return null;
        }

        public void StoreCalendar(string name, ICalendar calendar, bool replaceExisting, bool updateTriggers)
        {
            
        }

        public bool RemoveCalendar(string calName)
        {
            return false;
        }

        public ICalendar RetrieveCalendar(string calName)
        {
            return null;
        }


        public void TriggeredJobComplete(Trigger trigger, JobDetail jobDetail, SchedulerInstruction triggerInstCode)
        {
            
        }

        public virtual bool SupportsPersistence
	    {
	        get { return false; }
	    }

        public long EstimatedTimeToReleaseAndAcquireTrigger
        {
            get { return 5; }
        }

        public bool Clustered
        {
            get { return false; }
        }

        public string InstanceId
        {
            set {  }
        }

        public string InstanceName
        {
            set {  }
        }

        public virtual int GetNumberOfJobs()
        {
            return GetJobs().Count();
        }

		public virtual int GetNumberOfTriggers()
		{
            int triggerCount = 0;

            foreach(var jobFile in GetJobs())
            {
                Core.Domain.Job job = LoadJob(jobFile);
                triggerCount += job.Triggers.Count;
            }
           
            return triggerCount;
		}

		public virtual int GetNumberOfCalendars()
		{
		    return 0;
		}

		public virtual IList<string> GetJobNames(string groupName)
		{
		    var group = GetGroups().Where(d => d.Name == groupName).FirstOrDefault();
            if (group == null) throw new Exception(string.Format("The group '{0}' does not exist", groupName));

		    return GetJobs(group).Select(j => j.Name).ToList();
		}

        public IList<string> GetTriggerNames(string groupName)
        {
            return GetGroups().Where(g => g.Name == groupName).SelectMany(g => GetJobs(g).SelectMany(j => LoadJob(j).Triggers.Select(t => t.Name))).ToList();
        }

        public IList<string> GetJobGroupNames()
        {
            return GetGroups().Select(g => g.Name).ToList();
        }

        public IList<string> GetTriggerGroupNames()
        {
            return GetGroups().Select(g => g.Name).ToList();
        }

        public IList<string> GetCalendarNames()
        {
            return new List<string>();
        }

        public IList<Trigger> GetTriggersForJob(string jobName, string groupName)
        {
            return GetGroups().Where(g => g.Name == groupName).SelectMany(g => GetJobs(g).SelectMany(j => LoadJob(j).Triggers)).Select(
                trigger => ConvertTrigger(jobName, groupName, trigger)).ToList();
        }

        public TriggerState GetTriggerState(string triggerName, string triggerGroup)
        {
            return TriggerState.Error;
        }

        public void PauseTrigger(string triggerName, string groupName)
        {
            
        }

        public void PauseTriggerGroup(string groupName)
        {
            
        }

        public void PauseJob(string jobName, string groupName)
        {
            
        }

        public void PauseJobGroup(string groupName)
        {
            
        }

        public void ResumeTrigger(string triggerName, string groupName)
        {
            
        }

        public void ResumeTriggerGroup(string groupName)
        {
            
        }

        public ISet<string> GetPausedTriggerGroups()
        {
            return null;
        }

        public void ResumeJob(string jobName, string groupName)
        {
            
        }

        public void ResumeJobGroup(string groupName)
        {
            
        }

        public void PauseAll()
        {
            
        }

        public void ResumeAll()
        {
            
        }

        public IList<Trigger> AcquireNextTriggers(DateTimeOffset noLaterThan, int maxCount, TimeSpan timeWindow)
        {
            List<Trigger> result = new List<Trigger>();

            var timeTriggers = GetQuartzTriggers().ToList();
            if (timeTriggers.Count() == 0) return result;

            foreach (var tw in timeTriggers)
            {
                if (tw.GetNextFireTimeUtc() == null)
                {
                    continue;
                }

                if (tw.GetNextFireTimeUtc() > noLaterThan + timeWindow)
                {
                    //timeTriggers.Add(tw);
                    break;
                }

                //tw.state = InternalTriggerState.Acquired;

                //tw.FireInstanceId = GetFiredTriggerRecordId();
                //Trigger trig = (Trigger)tw.trigger.Clone();
                result.Add(tw);

                if (result.Count == maxCount)
                {
                    break;
                }
            }

            return result;
        }

        public void ReleaseAcquiredTrigger(Trigger trigger)
        {
            
        }

        public IList<TriggerFiredResult> TriggersFired(IList<Trigger> triggers)
        {
            List<TriggerFiredResult> results = new List<TriggerFiredResult>();

            foreach (Trigger trigger in triggers)
            {
                //TriggerWrapper tw = triggersByFQN[TriggerWrapper.GetTriggerNameKey(trigger)];
                //// was the trigger deleted since being acquired?
                //if (tw == null || tw.trigger == null)
                //{
                //    return null;
                //}
                //// was the trigger completed, paused, blocked, etc. since being acquired?
                //if (tw.state != InternalTriggerState.Acquired)
                //{
                //    return null;
                //}

                ICalendar cal = null;
                if (trigger.CalendarName != null)
                {
                    cal = RetrieveCalendar(trigger.CalendarName);
                    if (cal == null)
                        return null;
                }
                DateTimeOffset? prevFireTime = trigger.GetPreviousFireTimeUtc();
                // in case trigger was replaced between acquiring and firering
                //timeTriggers.Remove(tw);
                // call triggered on our copy, and the scheduler's copy
                //tw.trigger.Triggered(cal);
                trigger.Triggered(cal);
                //tw.state = TriggerWrapper.STATE_EXECUTING;
                //tw.state = InternalTriggerState.Waiting;

                TriggerFiredBundle bndle = new TriggerFiredBundle(RetrieveJob(trigger.JobName, trigger.JobGroup), trigger,
                                                                  cal,
                                                                  false, SystemTime.UtcNow(),
                                                                  trigger.GetPreviousFireTimeUtc(), prevFireTime,
                                                                  trigger.GetNextFireTimeUtc());

                JobDetail job = bndle.JobDetail;

                //if (job.Stateful)
                //{
                //    List<TriggerWrapper> trigs = GetTriggerWrappersForJob(job.Name, job.Group);
                //    foreach (TriggerWrapper ttw in trigs)
                //    {
                //        if (ttw.state == InternalTriggerState.Waiting)
                //        {
                //            ttw.state = InternalTriggerState.Blocked;
                //        }
                //        if (ttw.state == InternalTriggerState.Paused)
                //        {
                //            ttw.state = InternalTriggerState.PausedAndBlocked;
                //        }
                //        timeTriggers.Remove(ttw);
                //    }
                //    blockedJobs.Add(JobWrapper.GetJobNameKey(job));
                //}
                //else if (tw.trigger.GetNextFireTimeUtc() != null)
                //{
                //    lock (lockObject)
                //    {
                //        timeTriggers.Add(tw);
                //    }
                //}

                results.Add(new TriggerFiredResult(bndle));
            }
            return results;
        }
    }
}
